package agents.manager;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import agents.utils.Order;
import agents.utils.WorkPackage;
import agents.utils.WorkType;
import org.joda.time.DateTime;

/**
 * This class represents the manager of all system.
 * ManagerAgent is responsible for organize the reception of the order and send
 * it to production.
 * 
 * @author Group 6
 */
public class ManagerAgent extends Agent {
    
    // Preço de um único componente
    public static final int COMPONENT_MIN_PRICE_LATE = 80;
    public static final int COMPONENT_PRODUCTION_COST = 100;
    public static final int COMPONENT_PRICE_NORMAL = 120;
    public static final int COMPONENT_PRICE_OVERRIDE = 170;
    
    // Descrição de serviços
    public static final String SD_ORDERSERVICE_TYPE = "B2C";
    public static final String SD_ORDERSERVICE_NAME = "OrderService";
    
    // Lista de Conversações
    public static final String MSG_ORDER_REQUEST = "orderRequest";
    public static final String MSG_ORDER_DELIVERY = "orderDelivery";
    public static final String MSG_OFFER_WORK_PACKAGE = "offerWorkPackage";
    public static final String MSG_SEND_WORK_PACKAGE = "sendWorkPackage";
    public static final String MSG_RECEIVE_PROCESSED_WORK_PACKAGE = "receiveProcessedWorkPackage";
    
    public static final String MSG_CHECK_EXISTING_ORDER = "checkExistingOrder";
    
    private HashMap<Integer, Integer> lastOrdersQtyPrice;
    
    // Interface gráfico
    private ManagerAgentGUI gui;
    
    // Lista de ordens aceites para processamento (ordem chegada)
    private LinkedList<Order> ordersList;
    
    // Lista de máquinas ao dispor
    private ArrayList<AID> cuttingMachines;
    private ArrayList<AID> sewingMachines;
    private ArrayList<AID> packingMachines;
    
    // Queues de trabalho pendente
    private PriorityBlockingQueue<WorkPackage> cuttingWorkQueue;
    private PriorityBlockingQueue<WorkPackage> sewingWorkQueue;
    private PriorityBlockingQueue<WorkPackage> packingWorkQueue;
    
    // Conjunto de trabalho atribuído
    private HashMap<AID, WorkPackage> assignedCuttingWork;
    private HashMap<AID, WorkPackage> assignedSewingWork;
    private HashMap<AID, WorkPackage> assignedPackingWork;
    

    public ManagerAgent() {
        this.ordersList = new LinkedList<>();
        this.gui = new ManagerAgentGUI(this);
        this.ordersList = new LinkedList<>();
        this.cuttingMachines = new ArrayList<>();
        this.sewingMachines = new ArrayList<>();
        this.packingMachines = new ArrayList<>();
        this.cuttingWorkQueue = new PriorityBlockingQueue<>();
        this.sewingWorkQueue = new PriorityBlockingQueue<>();
        this.packingWorkQueue = new PriorityBlockingQueue<>();
        this.assignedCuttingWork = new HashMap<>();
        this.assignedSewingWork = new HashMap<>();
        this.assignedPackingWork = new HashMap<>();
    }
    
    @Override
    protected void setup() {
        super.setup();
        // Publicar serviços
        this.announcementServices();
        // Adicionar todos os comportamentos necessários à execução do agente
        this.addAllBehaviors();
    }
    
    private void announcementServices() {
        // Define o serviço "OrderService" que os clientes devem reconhecer
        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType(SD_ORDERSERVICE_TYPE);
        sd.setName(SD_ORDERSERVICE_NAME);
        dfd.addServices(sd);
        try {
            DFService.register(this, dfd);
        } catch (FIPAException ex) {
            ex.printStackTrace();
        }
    }
    
    private void addAllBehaviors() {
        
        addBehaviour(new CheckExistingOrder());
        
        // Lida com a negociação de encomendas
        addBehaviour(new OrderRequestsServer());
        
        
        // Lida com a entrega de encomendas de x em x milissegundos
        //addBehaviour(new CheckPossibleDelivery(this, 16000));
        
        // Procura máquinas de x em x milissegundos
        addBehaviour(new SearchMachines(this, 15000));
        // Distribui trabalho de x em x milissegundos
        addBehaviour(new WorkDistribuion(this, 5000));
        // Recebe trabalho
        addBehaviour(new WorkReception());
    }

    @Override
    protected void takeDown() {
        super.takeDown();
        // Remover announcements
        try {
            DFService.deregister(this);
        }
        catch (FIPAException ex) {
            ex.printStackTrace();
        }
    }
    
    private class CheckExistingOrder extends CyclicBehaviour {

        @Override
        public void action() {
            // Definir Performatives "compatíveis" com este comportamento
            MessageTemplate mt = MessageTemplate.and(
                    MessageTemplate.MatchConversationId(ManagerAgent.MSG_CHECK_EXISTING_ORDER),
                    MessageTemplate.MatchPerformative(ACLMessage.QUERY_IF));
            // Obter mensagem
            ACLMessage originalMsg = myAgent.receive(mt);
            // Se existir uma mensagem
            if (originalMsg != null) {
                //System.out.println("Manager - Recebi mensagem do client: " + originalMsg.getConversationId());
                ACLMessage replyMsg = originalMsg.createReply();
                replyMsg.setPerformative(ACLMessage.INFORM_IF);
                String replyMsgContent;
                // Verificar se existe uma encomenda pendente
                Order order = null;
                for (Order orderTemp : ordersList) {
                    if (orderTemp.getClientAID().getLocalName().equalsIgnoreCase(originalMsg.getSender().getLocalName())) {
                        order = orderTemp;
                    }
                }
                if (order == null) {
                    replyMsgContent = "NO;";
                } else {
                    replyMsgContent = "YES;";
                    if (order.isFinished()) {
                        replyMsgContent += "Finished;";
                        
                        gui.appendInfoToManagerOrderDeliveryCancellationLog("Finished and Delivered - Order (id:"
                            + order.getId()
                            + " | clientName: "
                            + order.getClientAID().getLocalName()
                            + " | quantity:"
                            + order.getQty()
                            + ")");
                    ordersList.remove(order);
                    gui.setActualOrders(ordersList);
                    gui.updateAllInfo();
                        
                    } else {
                        replyMsgContent += "Pending;";
                        int qtyF = order.getQtyFinished();
                        int qtyT = order.getQty();
                        int perCent = qtyF * 100;
                        perCent = perCent / qtyT;
                        replyMsgContent += (perCent +";");
                    }
                    replyMsgContent += (order.getQty() + ";");
                    replyMsgContent += (order.getPriceByQty() + ";");
                    replyMsgContent += (order.getDeadline());
                }
                replyMsg.setContent(replyMsgContent);
                myAgent.send(replyMsg);
                //System.out.println("Manager - Enviei mensagem para o client: " + replyMsg.getConversationId());
            } else {
                block();
            }
        }
    }
    
    /**
     * Verifica se a encomenda pode ser produzida dentro do prazo estabelecido.
     * Assumindo que apenas está a trabalhar uma máquina de cada tipo.
     * @param qty quantidade de produtos
     * @param startTime momento de início de produção
     * @param deadline prazo limite para a conclusão da produção
     * @return true, se for possível atingir o objectivo; false, caso contrário
     */
    private boolean withinDeadline(int qty, DateTime startTime, DateTime deadline) {
        int productionProcessingSecs = calculateProcessingSecsNeeded(qty);
        DateTime productionEndTime = startTime.plusSeconds(productionProcessingSecs);
        if (productionEndTime.compareTo(deadline) == -1) {
            return true;
        } else {
            return false;
        }
    }
    
    private int calculateProcessingSecsNeeded(int qty) {
        int productionProcessingMillis = WorkType.CUTTING.getDefaultTimeToProcess()
                + WorkType.SEWING.getDefaultTimeToProcess()
                + WorkType.PACKING.getDefaultTimeToProcess();
        int productionProcessingSecs = (int) (productionProcessingMillis / 1000);
        productionProcessingSecs = (int) (productionProcessingSecs * 1.3F);
        productionProcessingSecs *= qty;
        return productionProcessingSecs;
    }
    
    /*
    private boolean addOrder(AID clientAID, int qty, DateTime deadline) {
        boolean canAccept = false; 
        // Se a queue estiver vazia
        if (this.ordersList.isEmpty()) {
            // Verificar se há tempo de finalizar a encomenda antes da Deadline
            if (this.withinDeadline(qty, new DateTime(), deadline)) {
                // Se houver
                canAccept = true;
            } else {
                // Se não houver
                return canAccept;
            }
        }
        // Se a queue não estiver vazia
        else {
            Order lastOrderInQueue = this.ordersList.getLast();
            // Verificar se há tempo de finalizar a encomenda antes da Deadline
            if (this.withinDeadline(qty, lastOrderInQueue.getDeadline(), deadline)) {
                // Se houver
                canAccept = true;
            } else {
                // Se não houver
                return canAccept;
            }
        }
        // Se for possível aceitar a encomenda, prepara a encomenda de "forma inicial"
        if (canAccept) {
            // Adicionar à lista de ordens aceites para processamento
            Order tempOrder = new Order(clientAID, qty, 0, deadline);
            this.ordersList.add(tempOrder);
            // Gerar as workPackages para a primeira fase de trabalho
            for (int i = 0; i < tempOrder.getQty(); i++) {
                WorkPackage wp = new WorkPackage(tempOrder.getId(), WorkType.CUTTING, deadline);
                this.cuttingWorkQueue.add(wp);
            }
            // Actualizar interfaces
            gui.setActualOrders(ordersList);
            gui.setPendingCuttingWP(Integer.toString(cuttingWorkQueue.size()));
            gui.updateAllInfo();
            return true;
        } else {
            return false;
        }
    }*/
    
    private boolean addOrder(AID clientAID, int qty, int pricePerQty, DateTime deadline, Order.Priority priority) {
        // Adicionar à lista de ordens aceites para processamento
        Order tempOrder = new Order(clientAID, qty, pricePerQty, deadline, priority);
        this.ordersList.add(tempOrder);
        // Gerar as workPackages para a primeira fase de trabalho
        for (int i = 0; i < tempOrder.getQty(); i++) {
            WorkPackage wp = new WorkPackage(tempOrder.getId(), WorkType.CUTTING, deadline, priority);
            this.cuttingWorkQueue.add(wp);
        }
        // Actualizar interfaces
        gui.setActualOrders(ordersList);
        gui.setPendingCuttingWP(Integer.toString(cuttingWorkQueue.size()));
        gui.updateAllInfo();
        return true;
    }
    
    private int averageComponentPrice(int price) {
        // Calcular média dos preços
        int averagePrice = 0;
        if (this.lastOrdersQtyPrice.isEmpty()) {
            averagePrice = COMPONENT_PRICE_NORMAL;
        } else {
            int totalQty = 0;
            for(Integer i: this.lastOrdersQtyPrice.keySet()) {
                totalQty += i;
            }
            int totalPrice = 0;
            for(Integer i: this.lastOrdersQtyPrice.values()) {
                totalPrice += i;
            }
            averagePrice = (int) (totalPrice / totalQty);
        }
        return averagePrice;
    }
    
    private boolean insideNormalPrice(int price) {
        if (price >= COMPONENT_PRICE_NORMAL) {
            return true;
        } else {
            return false;
        }
    }
    
    private String belowNormalPrice(int price) {
        int tmpMaxPrice = (int) (COMPONENT_PRICE_NORMAL * 0.99F);
        int tmpMinPrice = (int) (COMPONENT_PRICE_NORMAL * 0.80F);
        // Se estiver entre o tmpMaxPrice e o COMPONENT_PRICE_NORMAL
        if ((tmpMaxPrice <= price) && (COMPONENT_PRICE_NORMAL >= price)) {
            return "Close";
        } else if ((tmpMinPrice <= price) && (tmpMaxPrice >= price)) {
            return "Close";
        } else {
            return "Far";
        }
    }
    
    private boolean insideOverridePrice(int price) {
        if (price >= COMPONENT_PRICE_OVERRIDE) {
            return true;
        } else {
            return false;
        }
    }
    
    private String belowOverridePrice(int price) {
        int tmpMaxPrice = (int) (COMPONENT_PRICE_OVERRIDE * 0.99F);
        int tmpMinPrice = (int) (COMPONENT_PRICE_OVERRIDE * 0.80F);
        // Se estiver entre o tmpMaxPrice e o COMPONENT_PRICE_OVERRIDE
        if ((tmpMaxPrice <= price) && (COMPONENT_PRICE_OVERRIDE >= price)) {
            return "Close";
        } else if ((tmpMinPrice <= price) && (tmpMaxPrice >= price)) {
            return "Close";
        } else {
            return "Far";
        }
    }
    
    private class OrderRequestsServer extends CyclicBehaviour {

        @Override
        public void action() {
            // Definir Performatives "compatíveis" com este comportamento
            MessageTemplate mt = MessageTemplate.and(
                    MessageTemplate.MatchConversationId(MSG_ORDER_REQUEST),
                    MessageTemplate.MatchPerformative(ACLMessage.REQUEST));
            // Obter mensagem
            ACLMessage originalMsg = myAgent.receive(mt);
            // Se existir uma mensagem
            if (originalMsg != null) {
                // Analisar e processar conteúdo da mensagem
                String[] msgArray = originalMsg.getContent().split(";");
                int productPrice = Integer.parseInt(msgArray[0]);
                int productQty = Integer.parseInt(msgArray[1]);
                DateTime orderDeadline = DateTime.parse(msgArray[2]);
                // Preparar resposta
                ACLMessage replyMsg = originalMsg.createReply();
                // Se não existirem ordens em produção/agendadadas
                if (ordersList.isEmpty()) {
                    // Verificar se há tempo de finalizar a encomenda antes da Deadline - Prioridade Normal
                    if (withinDeadline(productQty, new DateTime(), orderDeadline)) {
                        // Se houver, averiguar se o preço disposto a pagar pelo cliente é suficiente
                        if (insideNormalPrice(productPrice)) {
                            // É suficiente, por isso aceita a encomenda
                            addOrder(originalMsg.getSender(), productQty, productPrice, orderDeadline, Order.Priority.NORMAL);
                            replyMsg.setPerformative(ACLMessage.AGREE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Accepted Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        } else if (belowNormalPrice(productPrice).equals("Close")) {
                            replyMsg.setPerformative(ACLMessage.PROPOSE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Rejected Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        } else {
                            replyMsg.setPerformative(ACLMessage.REFUSE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Rejected Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        }
                    } else {
                        // Se não houver tempo
                        replyMsg.setPerformative(ACLMessage.REFUSE);
                        replyMsg.setContent("IMPOSSIBLE;N/A");
                        gui.appendInfoToManagerOrderRequestLog(
                                "Rejected Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                    }
                }
                // Se existirem ordens em produção/agendadadas
                else {
                    // Verificar se há necessidade de fazer Override às encomendas existentes
                    // 1º - Calcular o momento final de produção, em que o manager poderá ordenar a execução da ordem
                    DateTime productionFinishDT = new DateTime();
                    productionFinishDT.plusSeconds(calculateProcessingSecsNeeded(cuttingWorkQueue.size()));
                    // 2º - Adicionar o momento final de produção da nova order
                    productionFinishDT.plusSeconds(calculateProcessingSecsNeeded(productQty));
                    // Confirmar se há realmente tempo
                    
                    if (withinDeadline(productQty, productionFinishDT, orderDeadline)) {
                        // Há tempo, averiguar a "justiça" do preço oferecido por unidade
                        if (insideNormalPrice(productPrice)) {
                            // É suficiente, por isso aceita a encomenda
                            addOrder(originalMsg.getSender(), productQty, productPrice, orderDeadline, Order.Priority.NORMAL);
                            replyMsg.setPerformative(ACLMessage.AGREE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Accepted Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        } else if (belowNormalPrice(productPrice).equals("Close")) {
                            replyMsg.setPerformative(ACLMessage.PROPOSE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Rejected Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        } else {
                            replyMsg.setPerformative(ACLMessage.REFUSE);
                            replyMsg.setContent("OK;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                "Rejected Order from Client "
                                + originalMsg.getSender().getLocalName()
                                );
                        } 
                    } else {
                        // Verificar se é rentável atrasar a entrega de algumas encomendas para satisfazer uma ordem override
                        
                        // Calcular o lucro actual
                        DateTime productionTime = new DateTime();
                        
                        //HashMap<Order, Integer> countWP = new HashMap<>();
                        HashMap<Order, DateTime> deadlineWP = new HashMap<>();
                        
                        Iterator<WorkPackage> cuttingIt = cuttingWorkQueue.iterator();
                        
                        while (cuttingIt.hasNext()) {
                            // Actualizar tempo de produção
                            productionTime = productionTime.plusSeconds(calculateProcessingSecsNeeded(1));
                            
                            WorkPackage tempWP = cuttingIt.next();
                            // Pesquisar order
                            Order tempOrder = null;
                            Iterator<Order> orderIt = ordersList.iterator();
                            while (orderIt.hasNext() && (tempOrder == null)) {
                                Order orderItTemp = orderIt.next();
                                if (orderItTemp.getId() == tempWP.getOrderID()) {
                                    // Encontramos a Order
                                    tempOrder = orderItTemp;
                                }
                            }
                            deadlineWP.put(tempOrder, productionTime);
                        }
                        
                        int totalRevenueActual = 0;
                        Iterator<Order> deadlineWPIt = deadlineWP.keySet().iterator();
                        
                        while(deadlineWPIt.hasNext()) {
                            Order tempOrder = deadlineWPIt.next();
                            DateTime tempOrderProductionTime = deadlineWP.get(tempOrder);
                            // Obter o número de componentes
                            int componentNumber = tempOrder.getQty();
                            // Se a encomenda estiver dentro da deadline definida
                            if (tempOrder.getDeadline().isAfter(tempOrderProductionTime)) {
                                // Averiguar a receita total da ordem, multiplicando o número de componentes a produzir
                                // pelo custo do componente
                                if (tempOrder.getPriority() == Order.Priority.NORMAL) {
                                    // Se a encomenda foi negociada a preço NORMAL
                                    totalRevenueActual = totalRevenueActual + (componentNumber * COMPONENT_PRICE_NORMAL);
                                } else {
                                    // Se a encomenda foi negociada a preça OVERRIDE
                                    totalRevenueActual = totalRevenueActual + (componentNumber * COMPONENT_PRICE_OVERRIDE);
                                }
                            }
                            // Se a encomenda não estiver dentro da deadline definida
                            else {
                                // Tem que se calcular a receita, cobrando ao cliente um preço mais baixo,
                                // de forma a compensar o cliente pelo atraso
                                totalRevenueActual = totalRevenueActual + (componentNumber * COMPONENT_MIN_PRICE_LATE);
                            }
                        }
                        
                        
                        
                        
                        
                        
                        
                        // Previsão do lucro se aceitar a nova encomenda
                        productionTime = new DateTime();
                        
                        deadlineWP = new HashMap<>();
                        
                        // Previsão de nova queue de cuttingWPs após aceitar encomenda
                        PriorityBlockingQueue<WorkPackage> previewCuttingWorkQueue = new PriorityBlockingQueue<>(cuttingWorkQueue);
                        // Previsão de nova arraylist de orders após aceitar encomenda
                        ArrayList<Order> previewOrdersList = new ArrayList<>(ordersList);
                        // Adicionar WPs na PreviewCuttingWorkQueue
                        // Adicionar à lista de ordens aceites para processamento
                        Order previewTempOrder = new Order(originalMsg.getSender(), productQty, productPrice, orderDeadline, Order.Priority.HIGH);
                        previewOrdersList.add(previewTempOrder);
                        // Gerar as workPackages para a primeira fase de trabalho
                        for (int i = 0; i < previewTempOrder.getQty(); i++) {
                            WorkPackage wp = new WorkPackage(previewTempOrder.getId(), WorkType.CUTTING, orderDeadline, Order.Priority.HIGH);
                            previewCuttingWorkQueue.add(wp);
                        }
                        
                        
                        cuttingIt = previewCuttingWorkQueue.iterator();
                        
                        
                        while (cuttingIt.hasNext()) {
                            // Actualizar tempo de produção
                            productionTime = productionTime.plusSeconds(calculateProcessingSecsNeeded(1));
                            
                            WorkPackage tempWP = cuttingIt.next();
                            // Pesquisar order
                            Order tempOrder = null;
                            Iterator<Order> orderIt = previewOrdersList.iterator();
                            while (orderIt.hasNext() && (tempOrder == null)) {
                                Order orderItTemp = orderIt.next();
                                if (orderItTemp.getId() == tempWP.getOrderID()) {
                                    // Encontramos a Order
                                    tempOrder = orderItTemp;
                                }
                            }
                            deadlineWP.put(tempOrder, productionTime);
                        }
                        
                        int previewTotalRevenue = 0;
                        deadlineWPIt = deadlineWP.keySet().iterator();
                        
                        while(deadlineWPIt.hasNext()) {
                            Order tempOrder = deadlineWPIt.next();
                            DateTime tempOrderProductionTime = deadlineWP.get(tempOrder);
                            // Obter o número de componentes
                            int componentNumber = tempOrder.getQty();
                            // Se a encomenda estiver dentro da deadline definida
                            if (tempOrder.getDeadline().isAfter(tempOrderProductionTime)) {
                                // Averiguar a receita total da ordem, multiplicando o número de componentes a produzir
                                // pelo custo do componente
                                if (tempOrder.getPriority() == Order.Priority.NORMAL) {
                                    // Se a encomenda foi negociada a preço NORMAL
                                    previewTotalRevenue = previewTotalRevenue + (componentNumber * COMPONENT_PRICE_NORMAL);
                                } else {
                                    // Se a encomenda foi negociada a preça OVERRIDE
                                    previewTotalRevenue = previewTotalRevenue + (componentNumber * COMPONENT_PRICE_OVERRIDE);
                                }
                            }
                            // Se a encomenda não estiver dentro da deadline definida
                            else {
                                // Tem que se calcular a receita, cobrando ao cliente um preço mais baixo,
                                // de forma a compensar o cliente pelo atraso
                                previewTotalRevenue = previewTotalRevenue + (componentNumber * COMPONENT_MIN_PRICE_LATE);
                            }
                        }
                        
                        
                        // Comparar o lucro que é certo(totalRevenueActual) com
                        // o lucro que prevemos vir a ganhar (previewTotalRevenue) caso aceitemos a nova encomenda
                        
                        // Aqui, o manager pretende maximizar o lucro a longo prazo, ao mesmo tempo
                        // que maximiza o período de ocupação da linha de produção
                        
                        if (previewTotalRevenue >= totalRevenueActual) {
                            // Aceitamos produzir
                            // Vamos verificar se o preço por Component proposto já está ao nível do exigido para
                            // encomenda de Alta Prioridade
                            // Há tempo, averiguar a "justiça" do preço oferecido por unidade
                            if (insideOverridePrice(productPrice)) {
                                // É suficiente, por isso aceita a encomenda
                                addOrder(originalMsg.getSender(), productQty, productPrice, orderDeadline, Order.Priority.HIGH);
                                replyMsg.setPerformative(ACLMessage.AGREE);
                                replyMsg.setContent("OVERRIDE;N/A");
                                gui.appendInfoToManagerOrderRequestLog(
                                        "Accepted Order from Client "
                                        + originalMsg.getSender().getLocalName());
                            } else if (belowOverridePrice(productPrice).equals("Close")) {
                                replyMsg.setPerformative(ACLMessage.PROPOSE);
                                replyMsg.setContent("OVERRIDE;N/A");
                                gui.appendInfoToManagerOrderRequestLog(
                                        "Rejected Order from Client "
                                        + originalMsg.getSender().getLocalName());
                            } else {
                                replyMsg.setPerformative(ACLMessage.REFUSE);
                                replyMsg.setContent("OVERRIDE;N/A");
                                gui.appendInfoToManagerOrderRequestLog(
                                        "Rejected Order from Client "
                                        + originalMsg.getSender().getLocalName());
                            }
                        } else {
                            // Não aceitamos produzir
                            replyMsg.setPerformative(ACLMessage.REFUSE);
                            replyMsg.setContent("IMPOSSIBLE;N/A");
                            gui.appendInfoToManagerOrderRequestLog(
                                    "Rejected Order from Client "
                                    + originalMsg.getSender().getLocalName());
                        }
                    }
                }
                // Enviar resposta
                myAgent.send(replyMsg);
            } else {
                gui.setActualOrders(ordersList);
                gui.updateAllInfo();
                // Comportamento fica suspenso à espera de novas mensagens
                block();
            }
        }
    }

    private class SearchMachines extends TickerBehaviour {

        DFAgentDescription template = null;
        ServiceDescription sd = null;

        public SearchMachines(Agent a, long period) {
            super(a, period);
        }
        
        private void searchSpecificMachines(WorkType type) {
            sd = new ServiceDescription();
            sd.setType("B2B");
            sd.setName(type.name() + " Machine");
            template = new DFAgentDescription();
            template.addServices(sd);
            try {
                DFAgentDescription[] found = DFService.search(myAgent, template);
                if (type == WorkType.CUTTING) {
                    cuttingMachines = new ArrayList<>();
                    for (int i = 0; i < found.length; i++) {
                        cuttingMachines.add(found[i].getName());
                    }
                } else if (type == WorkType.SEWING) {
                    sewingMachines = new ArrayList<>();
                    for (int i = 0; i < found.length; i++) {
                        sewingMachines.add(found[i].getName());
                    }
                } else if (type == WorkType.PACKING) {
                    packingMachines = new ArrayList<>();
                    for (int i = 0; i < found.length; i++) {
                        packingMachines.add(found[i].getName());
                    }
                }
            } catch (FIPAException ex) {
                ex.printStackTrace();
            }
        }

        @Override
        protected void onTick() {
            searchSpecificMachines(WorkType.CUTTING);
            gui.setCuttingMachines(Integer.toString(cuttingMachines.size()));
            searchSpecificMachines(WorkType.SEWING);
            gui.setSewingMachines(Integer.toString(sewingMachines.size()));
            searchSpecificMachines(WorkType.PACKING);
            gui.setPackingMachines(Integer.toString(packingMachines.size()));
            gui.updateAllInfo();
        }
        
    }
    
    private class WorkDistribuion extends TickerBehaviour {

        public WorkDistribuion(Agent a, long period) {
            super(a, period);
        }

        @Override
        protected void onTick() {
            if (cuttingWorkQueue.size() > 0) {
                // Verificar as máquinas que não se encontram ocupadas (cutting)
                ArrayList<AID> avaliablecuttingMachines = new ArrayList<>();
                Iterator<AID> it = cuttingMachines.iterator();
                while (it.hasNext()) {
                    AID temp = it.next();
                    if (assignedCuttingWork.containsKey(temp) == false) {
                        avaliablecuttingMachines.add(temp);
                    }
                }
                // Enviar a cada uma das máquinas disponíveis uma workPackage
                for (AID aid : avaliablecuttingMachines) {
                    ACLMessage cfpMsg = new ACLMessage(ACLMessage.CFP);
                    cfpMsg.addReceiver(aid);
                    cfpMsg.setConversationId(ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    myAgent.send(cfpMsg);
                    MessageTemplate mt = MessageTemplate.MatchConversationId(
                            ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    // Aguardar por resposta
                    doWait(100);
                    ACLMessage proposalMsg = myAgent.receive(mt);
                    // Recebeu msg?
                    if (proposalMsg != null) {
                        // Se a machine aceitou
                        if (proposalMsg.getPerformative() == ACLMessage.PROPOSE) {
                            ACLMessage requestMsg = proposalMsg.createReply();
                            WorkPackage wTemp = cuttingWorkQueue.poll();
                            wTemp.setMachineAID(proposalMsg.getSender());
                            requestMsg.setPerformative(ACLMessage.REQUEST);
                            requestMsg.setConversationId(ManagerAgent.MSG_SEND_WORK_PACKAGE);
                            try {
                                requestMsg.setContentObject(wTemp);
                                myAgent.send(requestMsg);
                                assignedCuttingWork.put(proposalMsg.getSender(), wTemp);
                                gui.setPendingCuttingWP(Integer.toString(cuttingWorkQueue.size()));
                                gui.setAssignedCuttingWP(Integer.toString(assignedCuttingWork.size()));
                                gui.updateAllInfo();
                                gui.appendInfoToManagerWPDistributionLog("Sent - WP (id:"
                                        + wTemp.getId()
                                        + ") to Cutting Machine (name: "
                                        + proposalMsg.getSender().getLocalName()
                                        + ")");
                            } catch (IOException ex) {
                                Logger.getLogger(ManagerAgent.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        } else if (proposalMsg.getPerformative() == ACLMessage.REFUSE) {
                            gui.appendInfoToManagerWPDistributionLog("WP Refused - Cutting Machine - "
                                    + proposalMsg.getSender().getLocalName());
                        }
                    } else {
                        block();
                    }
                    gui.setActualOrders(ordersList);
                    gui.updateAllInfo();
                }
            }
            if (sewingWorkQueue.size() > 0) {
                // Verificar as máquinas que não se encontram ocupadas (cutting)
                ArrayList<AID> avaliablesewingMachines = new ArrayList<>();
                Iterator<AID> it = sewingMachines.iterator();
                while (it.hasNext()) {
                    AID temp = it.next();
                    if (assignedSewingWork.containsKey(temp) == false) {
                        avaliablesewingMachines.add(temp);
                    }
                }
                // Enviar a cada uma das máquinas disponíveis uma workPackage
                for (AID aid : avaliablesewingMachines) {
                    ACLMessage cfpMsg = new ACLMessage(ACLMessage.CFP);
                    cfpMsg.addReceiver(aid);
                    cfpMsg.setConversationId(ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    myAgent.send(cfpMsg);
                    MessageTemplate mt = MessageTemplate.MatchConversationId(
                            ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    // Aguardar por resposta
                    doWait(100);
                    ACLMessage proposalMsg = myAgent.receive(mt);
                    // Recebeu msg?
                    if (proposalMsg != null) {
                        // Se a machine aceitou
                        if (proposalMsg.getPerformative() == ACLMessage.PROPOSE) {
                            ACLMessage requestMsg = proposalMsg.createReply();
                            WorkPackage wTemp = sewingWorkQueue.poll();
                            wTemp.setMachineAID(proposalMsg.getSender());
                            requestMsg.setPerformative(ACLMessage.REQUEST);
                            requestMsg.setConversationId(ManagerAgent.MSG_SEND_WORK_PACKAGE);
                            try {
                                requestMsg.setContentObject(wTemp);
                                myAgent.send(requestMsg);
                                assignedSewingWork.put(proposalMsg.getSender(), wTemp);
                                gui.setPendingSewingWP(Integer.toString(sewingWorkQueue.size()));
                                gui.setAssignedSewingWP(Integer.toString(assignedSewingWork.size()));
                                gui.updateAllInfo();
                                gui.appendInfoToManagerWPDistributionLog("Sent - WP (id:"
                                        + wTemp.getId()
                                        + ") to Sewing Machine (name: "
                                        + proposalMsg.getSender().getLocalName()
                                        + ")");
                            } catch (IOException ex) {
                                Logger.getLogger(ManagerAgent.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        } else if (proposalMsg.getPerformative() == ACLMessage.REFUSE) {
                            gui.appendInfoToManagerWPDistributionLog("WP Refused - Sewing Machine - "
                                    + proposalMsg.getSender().getLocalName());
                        }
                    } else {
                        block();
                    }
                }
                gui.setActualOrders(ordersList);
                gui.updateAllInfo();
            }
            if (packingWorkQueue.size() > 0) {
                // Verificar as máquinas que não se encontram ocupadas (cutting)
                ArrayList<AID> avaliablepackingMachines = new ArrayList<>();
                Iterator<AID> it = packingMachines.iterator();
                while (it.hasNext()) {
                    AID temp = it.next();
                    if (assignedPackingWork.containsKey(temp) == false) {
                        avaliablepackingMachines.add(temp);
                    }
                }
                // Enviar a cada uma das máquinas disponíveis uma workPackage
                for (AID aid : avaliablepackingMachines) {
                    ACLMessage cfpMsg = new ACLMessage(ACLMessage.CFP);
                    cfpMsg.addReceiver(aid);
                    cfpMsg.setConversationId(ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    myAgent.send(cfpMsg);
                    MessageTemplate mt = MessageTemplate.MatchConversationId(
                            ManagerAgent.MSG_OFFER_WORK_PACKAGE);
                    // Aguardar por resposta
                    doWait(100);
                    ACLMessage proposalMsg = myAgent.receive(mt);
                    // Recebeu msg?
                    if (proposalMsg != null) {
                        // Se a machine aceitou
                        if (proposalMsg.getPerformative() == ACLMessage.PROPOSE) {
                            ACLMessage requestMsg = proposalMsg.createReply();
                            WorkPackage wTemp = packingWorkQueue.poll();
                            wTemp.setMachineAID(proposalMsg.getSender());
                            requestMsg.setPerformative(ACLMessage.REQUEST);
                            requestMsg.setConversationId(ManagerAgent.MSG_SEND_WORK_PACKAGE);
                            try {
                                requestMsg.setContentObject(wTemp);
                                myAgent.send(requestMsg);
                                assignedPackingWork.put(proposalMsg.getSender(), wTemp);
                                gui.setPendingPackingWP(Integer.toString(packingWorkQueue.size()));
                                gui.setAssignedPackingWP(Integer.toString(assignedPackingWork.size()));
                                gui.updateAllInfo();
                                gui.appendInfoToManagerWPDistributionLog("Sent - WP (id:"
                                        + wTemp.getId()
                                        + ") to Packing Machine (name: "
                                        + proposalMsg.getSender().getLocalName()
                                        + ")");
                            } catch (IOException ex) {
                                Logger.getLogger(ManagerAgent.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        } else if (proposalMsg.getPerformative() == ACLMessage.REFUSE) {
                            gui.appendInfoToManagerWPDistributionLog("WP Refused - Packing Machine - "
                                    + proposalMsg.getSender().getLocalName());
                        }
                    } else {
                        block();
                    }
                }
            }
            gui.setActualOrders(ordersList);
            gui.updateAllInfo();
        }
        
    }
    
    private class WorkReception extends CyclicBehaviour {

        @Override
        public void action() {
            MessageTemplate mt = MessageTemplate.and(
                    MessageTemplate.MatchPerformative(ACLMessage.INFORM),
                    MessageTemplate.MatchConversationId(ManagerAgent.MSG_RECEIVE_PROCESSED_WORK_PACKAGE));
            ACLMessage msgReceived = myAgent.receive(mt);
            if (msgReceived != null) {
                try {
                    WorkPackage wp = (WorkPackage) msgReceived.getContentObject();
                    if (wp.getWorkType() == WorkType.CUTTING) {
                        assignedCuttingWork.remove(msgReceived.getSender());
                        wp.setWorkType(WorkType.SEWING);
                        sewingWorkQueue.add(wp);
                        gui.setPendingCuttingWP(Integer.toString(cuttingWorkQueue.size()));
                        gui.setAssignedCuttingWP(Integer.toString(assignedCuttingWork.size()));
                        gui.setPendingSewingWP(Integer.toString(sewingWorkQueue.size()));
                        gui.appendInfoToManagerWPReceptionLog(
                                "Received Cutting WP (id: "
                                + wp.getId()
                                + ") from "
                                + msgReceived.getSender().getLocalName());
                    } else if (wp.getWorkType() == WorkType.SEWING) {
                        assignedSewingWork.remove(msgReceived.getSender());
                        wp.setWorkType(WorkType.PACKING);
                        packingWorkQueue.add(wp);
                        gui.setPendingSewingWP(Integer.toString(sewingWorkQueue.size()));
                        gui.setAssignedSewingWP(Integer.toString(assignedSewingWork.size()));
                        gui.setPendingPackingWP(Integer.toString(packingWorkQueue.size()));
                        gui.appendInfoToManagerWPReceptionLog(
                                "Received Sewing WP (id: "
                                + wp.getId()
                                + ") from "
                                + msgReceived.getSender().getLocalName());
                    } else if (wp.getWorkType() == WorkType.PACKING) {
                        assignedPackingWork.remove(msgReceived.getSender());
                        Iterator<Order> it = ordersList.iterator();
                        while (it.hasNext()) {
                            Order orderTemp = it.next();
                            if (orderTemp.getId() == wp.getOrderID()) {
                                orderTemp.increaseFinishedQty();
                            }
                        }
                        gui.setPendingPackingWP(Integer.toString(packingWorkQueue.size()));
                        gui.setAssignedPackingWP(Integer.toString(assignedPackingWork.size()));
                        gui.appendInfoToManagerWPReceptionLog(
                                "Received Packing WP (id: "
                                + wp.getId()
                                + ") from "
                                + msgReceived.getSender().getLocalName());
                    }
                    gui.setActualOrders(ordersList);
                    gui.updateAllInfo();
                } catch (UnreadableException ex) {
                    Logger.getLogger(ManagerAgent.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                block();
            }
        }
        
    }
    
    
    
    public void shutdownAgentAndGUI() {
        this.gui.setVisible(false);
        this.gui.dispose();
        this.gui = null;
        this.doDelete();
    }
    
}